package com.ouyangtao.jdbc;

import com.ouyangtao.constant.Constants;
import com.ouyangtao.conf.ConfigurationManager;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

/**
 * JDBC辅助组件
 *
 * 在正式的项目的代码编写过程中，是完全严格按照大公司的coding标准来的
 * 也就是说，在代码中，是不能出现任何hard code（硬编码）的字符
 * 比如“张三”、“com.mysql.jdbc.Driver”
 * 所有这些东西，都需要通过常量来封装和使用
 *
 * @author Administrator
 *
 */
public class JDBCHelper {

    // 第一步：在静态代码块中，直接加载数据库的驱动
    // 加载驱动，不是直接简单的，使用com.mysql.jdbc.Driver就可以了
    static {
        try {
            String driver = ConfigurationManager.getProperty(Constants.JDBC_DRIVER);
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    // 第二步，实现JDBCHelper的单例化
    // 为什么要实现代理化呢？因为它的内部要封装一个简单的内部的数据库连接池
    // 为了保证数据库连接池有且仅有一份，所以就通过单例的方式
    // 保证JDBCHelper只有一个实例，实例中只有一份数据库连接池
    private static JDBCHelper instance = null;

    public static JDBCHelper getInstance(){
        // 两步检查机制
        // 首先第一步，多个线程过来的时候，判断instance是否为null
        // 如果为null再往下走
        if (instance==null){
            // 在这里，进行多个线程的同步
            // 同一时间，只能有一个线程获取到Singleton Class对象的锁
            // 进入后续的代码
            // 其他线程，都是只能够在原地等待，获取锁
            synchronized (JDBCHelper.class){
                // 只有第一个获取到锁的线程，进入到这里，会发现是instance是null
                // 然后才会去创建这个单例
                // 此后，线程，哪怕是走到了这一步，也会发现instance已经不是null了
                // 就不会反复创建一个单例
                if (instance==null){

                 instance = new JDBCHelper();

                }
            }
        }
        return instance;
    }

    //数据库连接池
    private LinkedList<Connection> datasource = new LinkedList<Connection>();

    /**
     *
     * 第三步：实现单例的过程中，创建唯一的数据库连接池
     *
     * 私有化构造方法
     *
     * JDBCHelper在整个程序运行声明周期中，只会创建一次实例
     * 在这一次创建实例的过程中，就会调用JDBCHelper()构造方法
     * 此时，就可以在构造方法中，去创建自己唯一的一个数据库连接池
     *
     */
    private JDBCHelper(){
        //获取数据库连接池的大小，就是说，数据库连接池中要放多少个数据库连接
            int datasourceSize = ConfigurationManager.getInteger(Constants.JDBC_DATASOURCE_SIZE);
        // 然后创建指定数量的数据库连接，并放入数据库连接池中

        for (int i = 0; i < datasourceSize; i++) {
            boolean local = ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
            String url = null;
            String user = null;
            String password = null;

            if(local){
                 url = ConfigurationManager.getProperty(Constants.JDBC_URL);
                 user = ConfigurationManager.getProperty(Constants.JDBC_USER);
                 password = ConfigurationManager.getProperty(Constants.JDBC_PASSWORD);

            }else {
                url = ConfigurationManager.getProperty(Constants.JDBC_URL_PROD);
                user = ConfigurationManager.getProperty(Constants.JDBC_USER_PROD);
                password = ConfigurationManager.getProperty(Constants.JDBC_PASSWORD_PROD);
            }

            try {
                Connection connection = DriverManager.getConnection(url, user, password);
                datasource.push(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     第四步,提供获取数据库连接的方法
     有可能，你去获取的时候，这个时候，连接都被用光了，你暂时获取不到数据库连接
	 * 所以我们要自己编码实现一个简单的等待机制，去等待获取到数据库连接
    */
    public synchronized Connection getConnection(){
        while (datasource.size()==0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return datasource.poll();

    }

    /**
     * 第五步：开发增删改查的方法
     * 1、执行增删改SQL语句的方法
     * 2、执行查询SQL语句的方法
     * 3、批量执行SQL语句的方法
     */

    /**
     * 执行增删改SQL语句
     * @param sql
     * @param params
     * @return 影响的行数
     */
    public int executeUpdate(String sql,Object[] params){
        int rtn = 0;
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            connection = getConnection();
            connection.setAutoCommit(false);
            preparedStatement = connection.prepareStatement(sql);
            int paramsLength = params.length;
            if (preparedStatement!=null&&paramsLength>0){
                for (int i = 0; i < paramsLength ; i++) {
                    preparedStatement.setObject(i+1,params[i]);
                }
            }

            rtn = preparedStatement.executeUpdate();
            connection.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (connection!=null){
                datasource.push(connection);
            }
        }


        return rtn;
    }

    /**
     * 执行查询SQL语句
     * @param sql
     * @param params
     * @param callback
     */
    public void executeQuery(String sql, Object[] params,
                             QueryCallback callback) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);

            for(int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]);
            }
            rs = pstmt.executeQuery();
            callback.process(rs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(conn != null) {
                datasource.push(conn);
            }
        }
    }

    /**
     * 批量执行SQL语句
     *
     * 批量执行SQL语句，是JDBC中的一个高级功能
     * 默认情况下，每次执行一条SQL语句，就会通过网络连接，向MySQL发送一次请求
     *
     * 但是，如果在短时间内要执行多条结构完全一模一样的SQL，只是参数不同
     * 虽然使用PreparedStatement这种方式，可以只编译一次SQL，提高性能，但是，还是对于每次SQL
     * 都要向MySQL发送一次网络请求
     *
     * 可以通过批量执行SQL语句的功能优化这个性能
     * 一次性通过PreparedStatement发送多条SQL语句，比如100条、1000条，甚至上万条
     * 执行的时候，也仅仅编译一次就可以
     * 这种批量执行SQL语句的方式，可以大大提升性能
     *
     * @param sql
     * @param paramsList
     * @return 每条SQL语句影响的行数
     */
    public int[] executeBatch(String sql, List<Object[]> paramsList){
        int[] rtn = null;
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            //第一步：使用Connection对象，取消自动提交
            connection = getConnection();
            connection.setAutoCommit(false);
            preparedStatement = connection.prepareStatement(sql);

            // 第二步：使用PreparedStatement.addBatch()方法加入批量的SQL参数
            for (Object[] params : paramsList) {
                int paramsLength = params.length;
                for (int i = 0; i < paramsLength ; i++) {
                    preparedStatement.setObject(i+1,params[i]);
                }
                preparedStatement.addBatch();
            }


            // 第三步：使用PreparedStatement.executeBatch()方法，执行批量的SQL语句
            rtn = preparedStatement.executeBatch();

            // 最后一步：使用Connection对象，提交批量的SQL语句
            connection.commit();

        }catch (Exception e){
            e.printStackTrace();
        }

        return rtn;

    }



    //静态内部类,查询回调接口
    public  interface QueryCallback{

        void process(ResultSet rs) throws Exception;

    }


}
